%{
    #include <stdio.h>
    #include <stdint.h>
    #include <iostream>
    #include <vector>
    #include <map>
    #define LOG(...) ((void)(0))
    #if (YYDEBUG != 0)
    #include "debug.h"
    #endif

    using namespace std;

    #include "instruction.h"

    int yylex(void);
    extern void yyerror(const char *s);

    vector<Instruction> program;
    size_t progPos = 0;

    map<std::string, size_t> symbols;
    typedef map<std::string, size_t>::const_iterator  SymbolsIterator;

    Instruction currInstr;

%}
%union
{ /* SEMANTIC RECORD */
    const char* label;
    int  number;
}

%start statements
%token <label> T_LABEL
%token <number> T_INTEGER
%token <number> T_REGISTER
%token T_NEWLINE T_COMMA T_DOUBLE_DOT
%token T_DIR_AT T_DIR_END
%token T_OP_ADD T_OP_SUB T_OP_XOR T_OP_OR T_OP_AND T_OP_SWAP8 T_OP_SWAP16 T_OP_BSWAP
%token T_OP_SHIFTL T_OP_SHIFTR T_OP_SHIFTRA
%token T_OP_LOAD T_OP_STORE
%token T_OP_CMP T_OP_MOV T_OP_NOT T_OP_NEG
%token T_OP_JUMP_ABS T_OP_JUMP_REL
%token T_COND_EQUAL T_COND_NEQUAL T_COND_LESS T_COND_GR_EQ T_COND_GREATER T_COND_L_EQ
%token T_COND_CARRY T_COND_NCARRY
%type<number> regimm
%%

statements:
    /* empty */
    {
        LOG("[statements-><no statements>]\n");
    } |
    statements label statement T_NEWLINE
    {
        // it would be better label statement T_NEWLINE statements, but then Bison uses too much ram for stack.
        LOG("[statements->label statement statements] %li\n", (unsigned long)progPos);
    };

statement:
    /* empty */
    {
        LOG("[statement-><no statement>]\n");
    } |
    directive
    {
        LOG("[statement->directive;]\n");
    } |
    cond op
    {
        LOG("[statement->cond op;]\n");

        if(program.size() <= (progPos + 1))
        {
            cout << "neue groesse : " << progPos + 1 << endl;
            program.resize(progPos + 1);
        }

        program[progPos++] = currInstr;
        currInstr.clear();
    };

label:
    /* empty */
    {
         LOG("[label-><no label>]\n");
    } |
    T_LABEL T_DOUBLE_DOT
    {
        LOG("[label->T_LABEL]\n");
        symbols.insert(pair<std::string, size_t>(std::string($1), program.size()));
        free((void*)$1);
    }
    ;

directive:
    T_DIR_AT T_INTEGER
    {
        if(program.size() <= progPos) // resize our program
            program.resize(progPos);

        progPos = $2;
    } |
    T_DIR_END
    {
        YYACCEPT; // file done
    }
    ;

op: op3 T_REGISTER T_COMMA regimm T_COMMA T_REGISTER
    {
        LOG("[op->op3 T_REGISTER , regimm, T_REGISTER]\n");
        currInstr.setOpA($2);
        currInstr.setOpB($4);
        currInstr.setDest($6);
    } |
    op2 regimm T_COMMA T_REGISTER
    {
        LOG("[op->op2 regimm, T_REGISTER]\n");
        currInstr.setOpB($2);
        currInstr.setDest($4);
    } |
    op1 regimm
    {
        LOG("[op->op1 regimm]\n");
        currInstr.setOpB($2);
    } |
    T_OP_STORE regimm T_COMMA T_INTEGER T_COMMA T_REGISTER
    {
        /* op store is a special register order
        dmemAddrWrite <= opA;
		dmemNByteMask <= irSelDest(3 downto 0);
		dmemDataWrite <= opB;
		and we want to have an operation like
		st what, mask, where
		*/

        LOG("[op3->store]\n");
        currInstr.setClass(Instruction::CLASS_LDST);
        currInstr.setFunction(Instruction::F_LDST_STORE);

        currInstr.setOpB($2);
        currInstr.setDest($4);
        currInstr.setOpA($6);
    };

cond:
    {
        currInstr.setCond(Instruction::COND_ALWAYS);
    } |

    T_COND_NEQUAL
    {
        currInstr.setCond(Instruction::COND_NEQUAL);
        LOG("[cond->neq]\n");
    } |
    T_COND_EQUAL
    {
        currInstr.setCond(Instruction::COND_EQUAL);
        LOG("[cond->eq]\n");
    } |
    T_COND_LESS
    {
        currInstr.setCond(Instruction::COND_LESS);
        LOG("[cond->less]\n");
    } |
    T_COND_GR_EQ
    {
        currInstr.setCond(Instruction::COND_GR_EQ);
        LOG("[cond->gr_equal]\n");
    } |
    T_COND_GREATER
    {
        currInstr.setCond(Instruction::COND_GREATER);
        LOG("[cond->greater\n");
    } |
    T_COND_L_EQ
    {
        currInstr.setCond(Instruction::COND_L_EQ);
        LOG("[cond->less_eq\n");
    } |
    T_COND_CARRY
    {
        currInstr.setCond(Instruction::COND_CARRY);
        LOG("[cond->carry\n");
    } |
    T_COND_NCARRY
    {
        currInstr.setCond(Instruction::COND_NCARRY);
        LOG("[cond->not_carry\n");
    }
    ;

op1:
    T_OP_JUMP_ABS
    {
        currInstr.setClass(Instruction::CLASS_JUMP);
        currInstr.setFunction(Instruction::F_JUMP_ABS);
        LOG("[op1->jmpa\n");
    } |
    T_OP_JUMP_REL
    {
        currInstr.setClass(Instruction::CLASS_JUMP);
        currInstr.setFunction(Instruction::F_JUMP_REL);
        LOG("[op1->jmpr\n");
    };

op2:
    T_OP_MOV
    {
        currInstr.setClass(Instruction::CLASS_ALU);
        currInstr.setAluOp(Instruction::ALU_PASS_B);
        currInstr.setAluWriteBackBit(1);
        LOG("[op2->mov]\n");
    } |
    T_OP_CMP
    {
        currInstr.setClass(Instruction::CLASS_ALU);
        currInstr.setAluOp(Instruction::ALU_SUB);
        currInstr.setAluWriteBackBit(0);
        LOG("[op2->cmp]\n");
    } |
    T_OP_NEG
    {
        currInstr.setClass(Instruction::CLASS_ALU);
        currInstr.setAluOp(Instruction::ALU_NEG);
        currInstr.setAluWriteBackBit(1);
        LOG("[op2->neg]\n");
    } |
    T_OP_NOT
    {
        currInstr.setClass(Instruction::CLASS_ALU);
        currInstr.setAluOp(Instruction::ALU_NOT);
        currInstr.setAluWriteBackBit(1);
        LOG("[op2->not]\n");
    } |
    T_OP_SWAP16
    {
        currInstr.setClass(Instruction::CLASS_ALU);
        currInstr.setAluOp(Instruction::ALU_BSWAP);
        currInstr.setAluWriteBackBit(1);
        LOG("[op2->bswp]\n");
    } |
    T_OP_SWAP8
    {
        currInstr.setClass(Instruction::CLASS_ALU);
        currInstr.setAluOp(Instruction::ALU_BSWAP);
        currInstr.setAluWriteBackBit(1);
        LOG("[op2->bswp]\n");
    } |
    T_OP_BSWAP
    {
        currInstr.setClass(Instruction::CLASS_ALU);
        currInstr.setAluOp(Instruction::ALU_BSWAP);
        currInstr.setAluWriteBackBit(1);
        LOG("[op2->bswp]\n");
    }
    ;

op3:
    T_OP_ADD
    {
        currInstr.setClass(Instruction::CLASS_ALU);
        currInstr.setAluOp(Instruction::ALU_ADD);
        currInstr.setAluWriteBackBit(1);
        LOG("[op3->add]\n");
    } |
    T_OP_SUB
    {
        currInstr.setClass(Instruction::CLASS_ALU);
        currInstr.setAluOp(Instruction::ALU_SUB);
        currInstr.setAluWriteBackBit(1);
        LOG("[op3->sub]\n");
    } |
    T_OP_OR
    {
        currInstr.setClass(Instruction::CLASS_ALU);
        currInstr.setAluOp(Instruction::ALU_OR);
        currInstr.setAluWriteBackBit(1);
        LOG("[op3->or]\n");
    } |
    T_OP_XOR
    {
        currInstr.setClass(Instruction::CLASS_ALU);
        currInstr.setAluOp(Instruction::ALU_XOR);
        currInstr.setAluWriteBackBit(1);
        LOG("[op3->xor]\n");
    } |
    T_OP_AND
    {
        currInstr.setClass(Instruction::CLASS_ALU);
        currInstr.setAluOp(Instruction::ALU_AND);
        currInstr.setAluWriteBackBit(1);
        LOG("[op3->and]\n");
    } |
    T_OP_SHIFTR
    {
        currInstr.setClass(Instruction::CLASS_ALU);
        currInstr.setAluOp(Instruction::ALU_SHIFT_R);
        currInstr.setAluWriteBackBit(1);
        LOG("[op3->shift_right]\n");
    } |
    T_OP_SHIFTRA
    {
        currInstr.setClass(Instruction::CLASS_ALU);
        currInstr.setAluOp(Instruction::ALU_SHIFT_A);
        currInstr.setAluWriteBackBit(1);
        LOG("[op3->shift_right_arith]\n");
    } |
    T_OP_SHIFTL
    {
        currInstr.setClass(Instruction::CLASS_ALU);
        currInstr.setAluOp(Instruction::ALU_SHIFT_L);
        currInstr.setAluWriteBackBit(1);
        LOG("[op3->shift_left]\n");
    } |
    T_OP_LOAD
    {
        // load (from) addr, (with)mask, (to)what register.
        currInstr.setClass(Instruction::CLASS_LDST);
        currInstr.setFunction(Instruction::F_LDST_LOAD);
        LOG("[op3->load]\n");
    };

regimm :
    T_REGISTER
    {
        LOG("[regimm->register]\n");
        if($1 < 0 || $1 > 31)
            yyerror("register number is out of range");
        else
            $$ = $1;
    } |
    T_INTEGER
    {
        LOG("[regimm -> imm]\n");
        currInstr.setImmB(1);
        $$ = $1;

        if($1 < -16 || $1 > 31)
            yyerror("immediate is out of range");
    } |
    T_LABEL
    {
        LOG("[regimm ->bIsLabel]\n");
        currInstr.labelB = std::string($1);
        free((void*)$1);
        cout << "label is " << currInstr.labelB << " is empty? " << currInstr.labelB.empty() << endl;
    }
    ;

%%
